package lamma.helper;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import lamma.lab2.job.ProvidedBy;

/**
 * @author maxcellent
 *	
 *	use apache beanutils instead
 */
public class ReflectionHelper {
	
	/**
	 * @param object
	 * @param field
	 * @return
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	public static Object readField(Object object, Field field) throws IllegalArgumentException, IllegalAccessException {
		boolean accesiable = field.isAccessible();
		field.setAccessible(true);
		try {
			return field.get(object);	
		} finally {
			field.setAccessible(accesiable);
		}			
	}
	
	/**
	 * @param jobInstance
	 * @param outputFieldName
	 * @return
	 * @throws NoSuchFieldException 
	 * @throws IllegalAccessException 
	 * @throws SecurityException 
	 * @throws IllegalArgumentException 
	 */
	public static Object readField(Object object, String fieldNamePath) throws IllegalArgumentException, SecurityException, IllegalAccessException, NoSuchFieldException {
		String [] fullPath = ProvidedBy.Resolver.resolveFullPath(fieldNamePath);
		
		Object target = object;
		for (String pathToken : fullPath) {
			target = ReflectionHelper.readField(target, target.getClass().getDeclaredField(pathToken));
		}
		return target;
	}
	
	/**
	 * @param object
	 * @param fieldNames
	 * @return
	 * @throws IllegalArgumentException
	 * @throws SecurityException
	 * @throws IllegalAccessException
	 * @throws NoSuchFieldException
	 */
	public static Object [] readFields(Object object, String ...fieldNames) throws IllegalArgumentException, SecurityException, IllegalAccessException, NoSuchFieldException {
		Object [] fieldVals = new Object[fieldNames.length];
		for (int i = 0 ; i < fieldNames.length ; i ++) {
			fieldVals[i] = readField(object, fieldNames[i]);
		}
		return fieldVals;
	}
	
	/**
	 * @param object
	 * @param field
	 * @param value
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	public static void writeField(Object object, Field field, Object value) throws IllegalArgumentException, IllegalAccessException {
		boolean accesiable = field.isAccessible();
		field.setAccessible(true);
		field.set(object, value);
		field.setAccessible(accesiable);
	}

	/**
	 * @param object
	 * @param fieldName
	 * @param value
	 * @throws IllegalArgumentException
	 * @throws SecurityException
	 * @throws IllegalAccessException
	 * @throws NoSuchFieldException
	 */
	public static void writeField(Object object, String fieldName, Object value) throws IllegalArgumentException, SecurityException, IllegalAccessException, NoSuchFieldException {
		writeField(object, object.getClass().getDeclaredField(fieldName), value);
	}
	
	/**
	 * @param clazz
	 * @param methodName
	 * @param paramTypes
	 * @return
	 */
	public static Method lookupMethod(Class<?> clazz, String methodName, Class<?> ...paramTypes) {
		try {
			Method m = clazz.getDeclaredMethod(methodName, paramTypes);
			return m;
		} catch (SecurityException e) {
			e.printStackTrace();
			return null;
		} catch (NoSuchMethodException e) {
			for (Method method : clazz.getMethods()) {
				if (method.getName().equals(methodName) && method.getParameterTypes().length == paramTypes.length) {
					boolean isAssignable = true;
					
					for (int i = 0 ; i < paramTypes.length ; i ++) {
						if (!method.getParameterTypes()[i].isAssignableFrom(paramTypes[i])) {
							isAssignable = false;
							break;
						}	
					}
					
					if (isAssignable) {
						return method;
					}
				}
			}
			
			return null;
		}
	}
	
	public static Object invokeMethod(Object obj, String methodName, Object ...args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		Class<?> [] paramTypes = new Class<?>[args.length];
		for (int i = 0 ; i < args.length ; i ++) {
			paramTypes[i] = args[i].getClass();
		}
		
		Method m = lookupMethod(obj.getClass(), methodName, paramTypes);
		boolean accessible = m.isAccessible();
		m.setAccessible(true);
		Object result = m.invoke(obj, args);
		m.setAccessible(accessible);
		return result;
	}
	
	/**
	 * @param clazz
	 * @param paramTypes
	 * @return
	 */
	// TODO : solve case for non-static inner class case
	@SuppressWarnings("unchecked")
	public static <T> Constructor<T> lookupConstructor(Class<T> clazz, Class<?> ...paramTypes) {
		try {
			Constructor<T> c = clazz.getDeclaredConstructor(paramTypes);
			return c;
		} catch (SecurityException e) {
			e.printStackTrace();
			return null;
		} catch (NoSuchMethodException e) {
			for (Constructor<?> c : clazz.getDeclaredConstructors()) {
				if (c.getParameterTypes().length == paramTypes.length) {
					boolean isAssignable = true;
					
					for (int i = 0 ; i < paramTypes.length ; i ++) {
						if (!c.getParameterTypes()[i].isAssignableFrom(paramTypes[i])) {
							isAssignable = false;
							break;
						}	
					}
					
					if (isAssignable) {
						return (Constructor<T>) c;
					}
				}
			}
			
			return null;
		}
	}
	
	/**
	 * @param clazz
	 * @param args
	 * @return
	 * @throws IllegalArgumentException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	public static <T> T newInstance (Class<T> clazz, Object ...args) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
		Class<?> [] paramTypes = new Class<?> [args.length];
		for (int i = 0 ; i < args.length ; i ++) {
			// TODO : handle scenario when args[i] is null?
			paramTypes[i] = args[i].getClass();
		}
		
		Constructor<T> constructor = ReflectionHelper.lookupConstructor(clazz, paramTypes);
		
		boolean accessible = constructor.isAccessible();
		constructor.setAccessible(true);
		T instance = constructor.newInstance(args);
		constructor.setAccessible(accessible);
		return instance;
	}
}
